package org.micro.monitor;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.*;

public class ThreadStatistics {

    private static final Logger logger = LoggerFactory.getLogger(ThreadStatistics.class);

    /**
     * 调用响应过长统计，默认情况下，超过300ms响应的请求会被输出到日志中
     */
    private static int threshold = 300;
    private static final ThreadLocal<Boolean> IS_START = new InheritableThreadLocal<>();
    private static final ThreadLocal<StackTrace> STACK_TRACE = new InheritableThreadLocal<>();


    /**
     * 开始统计当前线程的执行延时
     *
     * @param message message name
     */
    public static void start(String message, int threshold) {
        // 调用的 response time （线程执行的时间）超过 threshold，打印错误日志，并dump堆栈
        ThreadStatistics.threshold = threshold;
        // 避免异常情况未清理，造成内存泄露
        STACK_TRACE.remove();
        // 创建当前线程的调用堆栈实例，用于记录关联路径的调用情况，关键路径通过全局的interceptor进行拦截
        STACK_TRACE.set(new StackTrace(message));
        // 开始标记
        IS_START.set(true);
    }

    /**
     * 结束统计，返回整个调用的耗时
     */
    public static Long stop() {
        if (STACK_TRACE.get() == null) {
            return null;
        }

        // 记录线程执行结束时间
        STACK_TRACE.get().end();
        // 记录延时，返回监控profile信息的filter
        long duration = STACK_TRACE.get().duration();
        // 结束标记
        IS_START.remove();
        // 清理 threadLocal 引用
        STACK_TRACE.remove();

        return duration;
    }

    /**
     * 结束统计, 整个调用的耗时，并打印结果日志
     *
     * @return {@link Pair}
     */
    public static Pair stopAndPrint() {
        if (STACK_TRACE.get() == null) {
            return null;
        }

        // 记录线程执行结束时间
        STACK_TRACE.get().end();
        // 记录延时，返回监控profile信息的filter
        long duration = STACK_TRACE.get().duration();
        // dump调用堆栈
        String dumpStack = STACK_TRACE.get().dump();
        // 结束标记
        IS_START.remove();
        //清理 threadLocal 引用
        STACK_TRACE.remove();

        return new Pair(dumpStack, duration);
    }

    /**
     * 方法进入时记录线程的执行堆栈信息，拦截器调用
     *
     * @param className  class name
     * @param methodName method name
     */
    public static void enter(String className, String methodName) {
        // 如果已经标记为开始，并且 stacktrace实例已经创建，则进行统计
        if ((IS_START.get() != null) && (STACK_TRACE.get() != null)) {
            STACK_TRACE.get().enter(new Entry(className, methodName, "enter"));
        }
    }

    /**
     * 方法return时记录线程的执行堆栈信息，拦截器调用
     */
    public static void exit() {
        // 如果已经标记为开始，并且 stacktrace实例已经创建，则进行统计
        if ((IS_START.get() != null) && (STACK_TRACE.get() != null)) {
            STACK_TRACE.get().exit();
        }
    }

    @Data
    @ToString
    @AllArgsConstructor
    private static class Pair implements Serializable {
        private String key;
        private Long value;
    }
    
    /**
     * 当前线程的线程的堆栈信息
     *
     * @author jipeng
     */
    public static final class StackTrace {

        private Stack<Entry> entryStack = new Stack<>();
        private List<Entry> entryList = new ArrayList<>();

        private String message;
        private long beginTime;
        private long endTime;
        private int level;

        /**
         * 线程执行开始
         *
         * @param message stack trace name
         */
        StackTrace(String message) {
            this.beginTime = System.currentTimeMillis();
            this.endTime = beginTime;
            this.message = message;
            this.level = 0;
        }

        /**
         * 记录方法的进入
         *
         * @param entry {@link Entry}
         */
        void enter(Entry entry) {
            entry.level = ++this.level;
            entryList.add(entry);
            entryStack.push(entry);
        }

        /**
         * 记录方法的返回
         */
        void exit() {
            this.level--;
            Entry entryPop = entryStack.pop();
            entryPop.exitTimestamp = System.currentTimeMillis();
        }

        /**
         * 线程执行结束
         */
        void end() {
            endTime = System.currentTimeMillis();
        }

        /**
         * 线程执行延时
         *
         * @return endTime-beginTime
         */
        long duration() {
            return endTime - beginTime;
        }

        String dump() {
            long dur = this.duration();
            if (dur > ThreadStatistics.threshold) {
                StringBuilder sb = new StringBuilder();
                sb.append("Total Delay [").append(duration()).append("ms] ").append(this.message).append("\n");

                List<Entry> entryLogList = new ArrayList<>(entryList);
                for (Entry entry : entryLogList) {
                    if (entry == null) {
                        continue;
                    }
                    for (int i = 0; i < entry.level; i++) {
                        sb.append("    ");
                    }
                    sb.append(entry.toString()).append("\n");
                }

                // 大于300毫秒的，才需要warn
                if (dur > ThreadStatistics.threshold) {
                    logger.warn("Response time [{}] exceed threshold [{}], request [{}], stack info:\n[{}]\n\n\n",
                            this.duration(), ThreadStatistics.threshold, this.message, sb.toString());
                }

                return sb.toString();
            }

            logger.debug("Response time [{}], request [{}], stack info\n[{}]", this.duration(), this.message, entryList.toString());
            return null;
        }

    }

    public static final class Entry {
        private String className;
        private String method;
        private long enterTimestamp;
        private String event;
        public int level;
        public long exitTimestamp;

        Entry(String className, String method, String event) {
            this.className = className;
            this.method = method;
            this.enterTimestamp = System.currentTimeMillis();
            this.exitTimestamp = this.enterTimestamp;
            this.event = event;
            this.level = 0;
        }

        @Override
        public String toString() {
            return "+---[" + (this.exitTimestamp - this.enterTimestamp) + "ms] - " +
                    className + "." + method +
                    " - [enter:" + this.enterTimestamp + ",exit:" + this.exitTimestamp + "]";
        }
    }

}
